/*Scala code license:

 Copyright 2009/10 Osman Pub

 Licensed under the Apache License, Version 2.0 (the "License") you may not use
 this file except in compliance with the License.

 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software distributed
 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.

 */
/**
 Orginal java code credits:
 * @author Abdul Bezrati
 */
package demos

import common._
import java.awt.event._
import javax.media.opengl._
import javax.media.opengl.glu.GLU
import javax.swing._

object Lesson09 {
  def main(args: Array[String]) {
    val glDisplay = GLDisplay("Lesson 09: Moving bitmaps in 3D space")
    glDisplay addGLEventListener new Renderer
    glDisplay addKeyListener new KeyAdapter {
      override def keyPressed(e: KeyEvent) =
        response = e getKeyCode match {
          case KeyEvent.VK_PAGE_UP => Action zoomIn
          case KeyEvent.VK_PAGE_DOWN => Action zoomOut
          case KeyEvent.VK_UP => Action decTilt
          case KeyEvent.VK_DOWN => Action incTilt
          case _ => Action none
        }

      override def keyReleased(e: KeyEvent) =
        e getKeyCode match {
          case KeyEvent.VK_ESCAPE => glDisplay stop
          case KeyEvent.VK_T => response = Action twinkle
          case _ => response = Action none
        }
    }
    glDisplay start
  }

  object Action extends Enumeration { val decTilt, incTilt, none, twinkle,
                                     zoomIn, zoomOut = Value }
  private var response = Action none
  private val NUMBER_OF_STARS = 50

  private class Renderer extends GLEventListener {
    /** Called by the drawable to initiate OpenGL rendering by the client.
     * After all GLEventListeners have been notified of a display event, the
     * drawable will swap its buffers if necessary.
     * @param gLDrawable The GLAutoDrawable object.
     */
    private val glu = new GLU
    private val stars = new Array[Star](NUMBER_OF_STARS)
    private val textures = new Array[Int](1)

    private var twinkle = false
    private var (spin, tilt, z) = (0.0f, 90.0f, -15.0f)

    def display(glDrawable: GLAutoDrawable) {
      def update {
        response match {
          case Action.twinkle => twinkle = !twinkle
          case Action.incTilt => tilt += 0.5f
          case Action.decTilt => tilt -= 0.5f
          case Action.zoomIn => z += 0.02f
          case Action.zoomOut => z -= 0.02f
          case _ =>
        }
        response = Action none
      }

      val gl = glDrawable.getGL
      gl glClear GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT
      gl glBindTexture(GL GL_TEXTURE_2D, textures(0))

      for(i <- 0 until stars.length) {
        gl glLoadIdentity; gl glTranslatef(0, 0, z)
        
        gl glRotatef(tilt, 1, 0, 0) // Tilt The View (Using The Value In 'tilt')
        gl glRotatef(stars(i) angle, 0, 1, 0)// Rotate To The Current Stars Angle
        gl glTranslatef(stars(i) dist, 0, 0)    // Move Forward On The X Plane
        gl glRotatef(-stars(i).angle, 0, 1, 0) // Cancel The Current Stars Angle
        gl glRotatef(-tilt, 1, 0, 0)               // Cancel The Screen Tilt

        if(twinkle) {
            gl glColor4ub(stars(stars.length - i - 1).r.asInstanceOf[Byte],
                          stars(stars.length - i - 1).g.asInstanceOf[Byte],
                          stars(stars.length - i - 1).b.asInstanceOf[Byte],
                          0xff.asInstanceOf[Byte])
            gl glBegin GL.GL_QUADS
            gl glTexCoord2f(0, 0); gl glVertex3f(-1, -1, 0)
            gl glTexCoord2f(1, 0); gl glVertex3f(1, -1, 0)
            gl glTexCoord2f(1, 1); gl glVertex3f(1, 1, 0)
            gl glTexCoord2f(0, 1); gl glVertex3f(-1, 1, 0)
            gl glEnd
        }

        gl glRotatef(spin, 0, 0, 1)
        gl glColor4ub(stars(i).r.asInstanceOf[Byte], stars(i).g.asInstanceOf[Byte],
                      stars(i).b.asInstanceOf[Byte], 0xff.asInstanceOf[Byte])
        gl glBegin GL.GL_QUADS; gl glTexCoord2f(0, 0)
        gl glVertex3f(-1, -1, 0); gl glTexCoord2f(1, 0)
        gl glVertex3f(1, -1, 0); gl glTexCoord2f(1, 1)
        gl glVertex3f(1, 1, 0); gl glTexCoord2f(0, 1)
        gl glVertex3f(-1, 1, 0)
        gl glEnd

        spin += 0.01f
        stars(i).angle += i.asInstanceOf[Float] / stars.length
        stars(i).dist -= 0.01f

        if(stars(i).dist < 0) {
          stars(i).dist += 5
          stars(i).r = (scala.math.random * 1000).asInstanceOf[Int] & 0xff
          stars(i).g = (scala.math.random * 1000).asInstanceOf[Int] & 0xff
          stars(i).b = (scala.math.random * 1000).asInstanceOf[Int] & 0xff
        }
      }

      if(response != Action.none) update
    }

    /** Called when the display mode has been changed.  <B>!!
     * CURRENTLY UNIMPLEMENTED IN JOGL !!</B>
     * @param gLDrawable The GLAutoDrawable object.
     * @param modeChanged Indicates if the video mode has changed.
     * @param deviceChanged Indicates if the video device has changed.
     */
    def displayChanged(glDrawable: GLAutoDrawable, modeChanged: Boolean,
                       deviceChanged: Boolean) {}

    /** Called by the drawable immediately after the OpenGL context is
     * initialized for the first time. Can be used to perform one-time OpenGL
     * initialization such as setup of lights and display lists.
     * @param gLDrawable The GLAutoDrawable object.
     */
    def init(glDrawable: GLAutoDrawable) {
      val gl = glDrawable.getGL

      def loadGLTextures {
        val textImg = TextureReader readTexture "demos/data/images/star.bmp"

        //Create Nearest Filtered Texture
        gl glGenTextures(1, textures, 0)
        gl glBindTexture(GL GL_TEXTURE_2D, textures(0))
        gl glTexParameteri(GL GL_TEXTURE_2D, GL GL_TEXTURE_MAG_FILTER,
                           GL GL_LINEAR)
        gl glTexParameteri(GL GL_TEXTURE_2D, GL GL_TEXTURE_MIN_FILTER,
                           GL GL_LINEAR)
        gl glTexImage2D(GL GL_TEXTURE_2D, 0, GL GL_RGB, textImg width,
          textImg height, 0,  GL GL_RGB, GL GL_UNSIGNED_BYTE, textImg pixels)
      }

      // init
      gl glShadeModel GL.GL_SMOOTH               // Enable Smooth Shading
      gl glClearColor(0, 0, 0, 0)     // Black Background
      gl glClearDepth 1                       // Depth Buffer Setup
      gl glEnable GL.GL_DEPTH_TEST 							// Enables Depth Testing
      gl glHint(GL GL_PERSPECTIVE_CORRECTION_HINT, GL GL_NICEST)
      gl glBlendFunc(GL GL_SRC_ALPHA, GL GL_ONE)      
      gl glEnable GL.GL_BLEND

      for(i <- 0 until stars.length) {
          stars(i) = new Star
          stars(i) angle = 0f
          stars(i) dist = (i.asInstanceOf[Float] / stars.length) * 5f
          stars(i) r = (scala.math.random * 256).asInstanceOf[Int] 
          stars(i) g = (scala.math.random * 256).asInstanceOf[Int]
          stars(i) b = (scala.math.random * 256).asInstanceOf[Int] 
      }
      loadGLTextures
    }

    /** Called by the drawable during the first repaint after the component has
     * been resized. The client can update the viewport and view volume of the
     * window appropriately, for example by a call to
     * GL.glViewport(int, int, int, int) note that for convenience the component
     * has already called GL.glViewport(int, int, int, int)(x, y, width, height)
     * when this method is called, so the client may not have to do anything in
     * this method.
     * @param gLDrawable The GLAutoDrawable object.
     * @param x The X Coordinate of the viewport rectangle.
     * @param y The Y coordinate of the viewport rectanble.
     * @param width The new width of the window.
     * @param height The new height of the window.
     */
    def reshape(glDrawable: GLAutoDrawable, x: Int, y: Int, width: Int,
                height: Int) {
      require(height != 0)

      val gl = glDrawable.getGL

      gl glViewport(0, 0, width, height)
      gl glMatrixMode(GL GL_PROJECTION)
      gl glLoadIdentity

      glu gluPerspective(45, width / height, 1, 1000)
      gl glMatrixMode(GL GL_MODELVIEW)
      gl glLoadIdentity
    }

    private class Star(var r: Int = 0, var g: Int = 0, var b: Int = 0,
                       var dist: Float = 0, var angle: Float = 0) {}
  }
}